# coding=utf-8
from datetime import datetime, timedelta
import logging
from sqlalchemy import and_
import re

from ccc.dba import db_factory
from ccc.db import User, UserStatus, UserStatusLog, UserOvertime, OvertimeType, WeekendOut, WeekendOutType, \
    LocationRecord, TaskRecord
from ccc.settings import REGISTER_SECRET, ROOT_NAME, DEPARTMENT_NAME

_HANDLERS = dict()
UNKNOWN_PEOPLE_ERROR = u'我不认识您'
LOG = logging.getLogger(__name__)
WEEKDAYS = [u'星期一', u'星期二', u'星期三', u'星期四', u'星期五', u'星期六', u'星期日']


def check_weixin_id(weixin_id):
    with db_factory.create_session() as session:
        user = session.query(User).filter(User.weixin_id == weixin_id).scalar()
        return user is not None


def format_help():
    def cmd_str(fn):
        result = u''
        if fn.desc:
            result += u'# ' + fn.desc + u'\n'
        result += fn.name + u' ' + fn.arg_desc
        return result

    return u'可用命令包括：\n{}'.format(u'\n'.join(sorted(map(cmd_str, _HANDLERS.values()))))


def handle_request(msg, cmd, *args):
    h = _HANDLERS.get(cmd, None)
    if h is None:
        if msg.source_user == ROOT_NAME or check_weixin_id(msg.source_user):
            return u'未知命令：“{}”。{}'.format(cmd, format_help())
        else:
            return UNKNOWN_PEOPLE_ERROR
    else:
        try:
            if h.command_type == 'self':
                r = h(msg.source_user, *args)
            else:
                if msg.source_user == ROOT_NAME or check_weixin_id(msg.source_user):
                    r = h(*args)
                else:
                    return UNKNOWN_PEOPLE_ERROR
            if r is not None:
                if r == u'':
                    return u'[结果为空]'
                else:
                    return r
            else:
                return u'已完成{}： {}'.format(cmd, u' '.join(args))
        except Exception, ex:
            return u'ERROR: {}'.format(unicode(ex))


def self_command(name, arg_desc, desc=None):
    def _wrapper(fn):
        _HANDLERS[name] = fn
        fn.name = name
        fn.command_type = 'self'
        fn.arg_desc = arg_desc
        fn.desc = desc
        return fn

    return _wrapper


def auto_self_command(name, arg_desc=u'', desc=u''):
    def _wrapper(fn):
        def __wrapper(weixin_id, *args):
            with db_factory.create_session() as session:
                user = session.query(User).filter(User.weixin_id == weixin_id).scalar()
                if not user:
                    LOG.debug('user with weixin_id %s not found', weixin_id)
                    return UNKNOWN_PEOPLE_ERROR
                else:
                    return fn(session, user, *args)

        _HANDLERS[name] = __wrapper
        __wrapper.name = name
        __wrapper.command_type = 'self'
        __wrapper.arg_desc = arg_desc
        __wrapper.desc = desc
        return __wrapper

    return _wrapper


@self_command(u'申请', u'<姓名> <暗号，请向管理员索取>')
def on_request(weixin_id, name, register_secret):
    with db_factory.create_session() as session:
        user = session.query(User).filter(User.weixin_id == weixin_id).scalar()
        if user is not None:
            user.weixin_id = weixin_id
            return u'您早已是一名光荣的革命小将了'

        if register_secret != REGISTER_SECRET:
            LOG.debug('register secret wrong')
            return UNKNOWN_PEOPLE_ERROR

        session.add(
            User.new_user(name, u'男', '', weixin_id)
        )

    return u'恭喜您称为一名光荣的革命小将'


def status_change_command(action, status):
    def _wrapper(session, user):
        if user.status == UserStatus.GRADUATED:
            return u'您已经毕业了，不要乱搞！'

        user.status = status
        session.add(UserStatusLog.new_log(user))
        return u'已登记{}，当前状态为{}'.format(action, status)

    return _wrapper


for status in UserStatus.normal_leave:
    auto_self_command(status, u'')(status_change_command(status, status))

auto_self_command(u'销假', u'')(status_change_command(u'销假', UserStatus.OK))


def overtime_command(overtime_type):
    def _wrapper(session, user):
        session.add(UserOvertime.new(user, overtime_type))
        return u'已登记{}'.format(overtime_type)

    return _wrapper


for ot in OvertimeType.names:
    auto_self_command(ot, u'')(overtime_command(ot))


@auto_self_command(u'改名', u'<姓名>', u'修改自己的姓名')
def on_rename(session, user, name):
    user.name = name


@auto_self_command(u'本人加班')
def on_query_self_overtime(session, user):
    today = datetime.now().day
    overtimes = session.query(UserOvertime).filter(and_(UserOvertime.user == user, UserOvertime.date >= today)).all()
    overtimes = sorted(overtimes, key=lambda o: o.date)
    return u'\n'.join([u'{}({}) {}'.format(o.date, WEEKDAYS[o.date.weekday()], o.type) for o in overtimes])


@auto_self_command(u'全天加班', u'', u'登记' + u'，'.join(OvertimeType.names))
def on_day_overtime(session, user):
    for overtime_type in OvertimeType.names:
        session.add(UserOvertime.new(user, overtime_type))


WEEKDAY_DICT = {
    u'一': 0,
    u'二': 1,
    u'三': 2,
    u'四': 3,
    u'五': 4,
    u'六': 5,
    u'日': 6
}


@auto_self_command(u'本周加班', u'[周一][周二][周三][周四][周五][周六][周日] [早][中][晚]', u'登记本周的加班，不提供参数的话默认周一到周五')
def on_week_overtime(session, user, weekdays=u'周一周二周三周四周五', types=u'早中晚'):
    if user.status == UserStatus.GRADUATED:
        return u'您已经毕业了，不要乱搞！'

    to_book_days = set()
    weekdays = weekdays.replace(u'周', u'')
    today = datetime.today()
    today_weekday = today.weekday()
    for c in weekdays:
        if not WEEKDAY_DICT.has_key(c):
            raise Exception(u'这是星期几？“{}”'.format(c))
        weekday_num = WEEKDAY_DICT[c]

        if weekday_num < today_weekday:
            continue

        day = today + timedelta(days=weekday_num - today_weekday)
        to_book_days.add(day)

    overtime_types = set()
    for s in types:
        overtime_types.add(OvertimeType.value_of(s))

    for day in to_book_days:
        for overtime_type in overtime_types:
            session.add(UserOvertime.new(user, overtime_type, day))


@auto_self_command(u'取消加班', u'')
def on_cancel_overtime(session, user):
    if user.status == UserStatus.GRADUATED:
        return u'您已经毕业了，不要乱搞！'

    overtimes = session.query(UserOvertime).filter(UserOvertime.user == user).all()
    for overtime in overtimes:
        session.delete(overtime)

    return u'已取消所有加班记录'


@auto_self_command(u'周末外出', u'<时间，周六/周日/周六周日> <地点>', u'小将报周末外出')
def on_book_weekend_out(session, user, days, location):
    if user.status == UserStatus.GRADUATED:
        return u'您已经毕业了，不要乱搞！'

    _days_dict = {
        u'周六': WeekendOutType.Sat,
        u'周日': WeekendOutType.Sun,
        u'周六周日': WeekendOutType.SatSun,
        u'周六日': WeekendOutType.SatSun
    }

    type_ = _days_dict.get(days)
    if not type_:
        return u'失败：时间格式不对'

    current_weekend_date = WeekendOut.current_weekend_date()
    weekend_out = session.query(WeekendOut).filter(
        and_(WeekendOut.user == user, WeekendOut.weekend_date == current_weekend_date)).scalar()
    if weekend_out:
        weekend_out.type = type_
        weekend_out.book_time = datetime.now()
        weekend_out.location = location
    else:
        weekend_out = WeekendOut.new(user, type_, location)
        session.add(weekend_out)


@auto_self_command(u'报位置', u'<地点>')
def on_location_record(session, user, location, latitude=None, longitude=None):
    if user.status == UserStatus.GRADUATED:
        return u'您已经毕业了，不要乱搞！'

    session.add(LocationRecord.new(user, location=location, latitude=latitude, longitude=longitude))


def admin_command(name, arg_desc, desc=None):
    def _wrapper(fn):
        _HANDLERS[name] = fn
        fn.name = name
        fn.command_type = 'admin'
        fn.arg_desc = arg_desc
        fn.desc = desc
        return fn

    return _wrapper


@admin_command(u'注册', u'<姓名> [性别, 男/女] [手机号] [微信号, OpenID]')
def on_register(name, gender=u'男', mobile='', weixin_id=''):
    with db_factory.create_session() as session:
        session.add(
            User.new_user(name, gender, mobile, weixin_id)
        )


@admin_command(u'注销', u'<姓名>')
def on_unregiseter(name):
    with db_factory.create_session() as session:
        user = session.query(User).filter(User.name == name).scalar()
        if user:
            for log in user.logs:
                session.delete(log)

            for overtime in user.overtimes:
                session.delete(overtime)

            session.delete(user)
        else:
            return u'未找到此人：{}'.format(name)


@admin_command(u'登记', u'<姓名> <操作>', u'大哥帮忙登记在位情况')
def on_book(name, action):
    with db_factory.create_session() as session:
        user = session.query(User).filter(User.name == name).scalar()
        if not user:
            return u'未找到此人：{}'.format(name)

        for status in UserStatus.names:
            if action == status:
                user.status = status
                session.add(UserStatusLog.new_log(user, status))
                return u'已登记[{}]{}'.format(name, action)

        overtime = None
        if action == u'加班':
            now = datetime.now()
            if now.hour < 15:
                overtime = OvertimeType.NOON
            else:
                overtime = OvertimeType.NIGHT
        elif action == OvertimeType.NOON:
            overtime = OvertimeType.NOON
        elif action == OvertimeType.NIGHT:
            overtime = OvertimeType.NIGHT

        if overtime:
            session.add(UserOvertime.new(name, overtime))
            return u'已登记[{}]{}'.format(name, action)

        if action == u'销假':
            user.status = UserStatus.OK
            session.add(UserStatusLog.new_log(user, UserStatus.OK))
            return u'已登记[{}]{}'.format(name, action)

        return u'未知操作：{}'.format(action)


@admin_command(u'查人', u'', u'查询在位情况，不包括加班人员')
def on_report_normal():
    return generate_report(ignore_overtime=True)


@admin_command(u'完整查人', u'', u'查询在位情况，列出在位人员名单，加班算在位')
def on_report_normal():
    return generate_report(ignore_overtime=True, report_ok=True)


@admin_command(u'报人', u'', u'查询在位人员，包括加班人员')
def on_report():
    return generate_report()


@admin_command(u'完整报人', u'', u'查询在位情况，列出在位人员名单')
def on_report():
    return generate_report(report_ok=True)


def generate_report(ignore_overtime=False, report_ok=False):
    with db_factory.create_session() as session:
        users = session.query(User).all()
        users = filter(lambda u: u.status in UserStatus.report_names, users)
        ok_count = len(filter(lambda u: u.status == UserStatus.OK, users))
        total_count = len(users)

        now = datetime.now()
        if now.hour < 9:
            overtime_type = OvertimeType.MORNING
        elif now.hour < 15:
            overtime_type = OvertimeType.NOON
        else:
            overtime_type = OvertimeType.NIGHT
        overtime_users = session.query(UserOvertime).filter(and_(UserOvertime.date == now.date(),
                                                                 UserOvertime.type == overtime_type)).all()
        if ignore_overtime:
            overtime_user_names = []
        else:
            overtime_users = filter(lambda u: u.user.status == UserStatus.OK, overtime_users)
            overtime_user_names = map(lambda u: u'{} {}'.format(u.user.name, u.user.overtime_place), overtime_users)
            overtime_user_names = sorted(list(set(overtime_user_names)))

        overtime_count = len(overtime_user_names)
        ok_count -= overtime_count

        message = u'{}应到{}人，实到{}人'.format(DEPARTMENT_NAME, total_count, ok_count)
        append_messages = []
        if ok_count != total_count:
            if overtime_count:
                message += u'，{}{}人'.format(overtime_type, overtime_count)
                append_messages.append(u'{}{}人：{}'.format(overtime_type, overtime_count, u'，'.join(overtime_user_names)))

            for status in UserStatus.normal_leave:
                leave_user_names = map(lambda u: u.name, filter(lambda u: u.status == status, users))
                leave_count = len(leave_user_names)
                if leave_count:
                    message += u'，{}{}人'.format(status, leave_count)
                    append_messages.append(u'{}{}人：{}'.format(status, leave_count, u'，'.join(leave_user_names)))

            message += u'。\n'

        if report_ok:
            ok_user_names = map(lambda u: u.name, filter(lambda u: u.status == UserStatus.OK, users))
            if not ignore_overtime:
                raw_overtime_user_names = map(lambda u: u.user.name, overtime_users)
                ok_user_names = set(ok_user_names) - set(raw_overtime_user_names)
            ok_user_names = sorted(list(ok_user_names))
            append_messages.append(u'在位{}人：{}'.format(len(ok_user_names), u'，'.join(ok_user_names)))

        return message + u'；\n'.join(append_messages) + u"。"


def generate_report_v1(ignore_overtime=False, report_ok=False):
    with db_factory.create_session() as session:
        users = session.query(User).all()
        users = filter(lambda u: u.status in UserStatus.report_names, users)
        ok_count = len(filter(lambda u: u.status == UserStatus.OK, users))
        total_count = len(users)

        now = datetime.now()
        if now.hour < 9:
            overtime_type = OvertimeType.MORNING
        elif now.hour < 15:
            overtime_type = OvertimeType.NOON
        else:
            overtime_type = OvertimeType.NIGHT
        overtime_users = session.query(UserOvertime).filter(and_(UserOvertime.date == now.date(),
                                                                 UserOvertime.type == overtime_type)).all()
        if ignore_overtime:
            overtime_user_names = []
        else:
            overtime_users = filter(lambda u: u.user.status == UserStatus.OK, overtime_users)
            overtime_user_names = map(lambda u: u.user.name, overtime_users)
            overtime_user_names = sorted(list(set(overtime_user_names)))

        overtime_count = len(overtime_user_names)
        ok_count -= overtime_count

        if report_ok:
            ok_user_names = map(lambda u: u.name, filter(lambda u: u.status == UserStatus.OK, users))
            ok_user_names = set(ok_user_names) - set(overtime_user_names)
            ok_user_names = sorted(list(ok_user_names))
            ok_users_string = u'（{}）'.format(u'，'.join(ok_user_names))
        else:
            ok_users_string = u''
        message = u'{}: 应到{}人，实到{}人{}。'.format(DEPARTMENT_NAME, total_count, ok_count, ok_users_string)
        if ok_count != total_count:
            message += u'其中'
            for status in UserStatus.normal_leave:
                leave_user_names = map(lambda u: u.name, filter(lambda u: u.status == status, users))
                leave_count = len(leave_user_names)
                if leave_count:
                    message += u'，{}人{}（{}）'.format(leave_count, status, u'，'.join(leave_user_names))

            if overtime_count:
                message += u'，{}人{}（{}）'.format(overtime_count, overtime_type, u'，'.join(overtime_user_names))
            message += u'。'

        return message


@admin_command(u'名单', u'')
def on_list():
    with db_factory.create_session() as session:
        users = session.query(User).all()
        users = filter(lambda u: u.status in UserStatus.report_names, users)
        return u'\n'.join(sorted(map(lambda u: u.name, users)))


@admin_command(u'当前在位', u'')
def on_query_ok():
    with db_factory.create_session() as session:
        users = session.query(User).filter(User.status == UserStatus.OK).all()
        return u'\n'.join(sorted(map(lambda u: u.name, users)))


@admin_command(u'当前状态', u'')
def on_list_status():
    with db_factory.create_session() as session:
        users = session.query(User).all()
        users = filter(lambda u: u.status in UserStatus.report_names, users)
        return u'\n'.join(sorted(map(lambda u: u'{} {}'.format(u.name, u.status), users)))


@admin_command(u'今日变更', u'')
def on_query_change_today():
    with db_factory.create_session() as session:
        today = datetime.now().date()
        logs = session.query(UserStatusLog).all()
        logs = filter(lambda l: l.update_time.date() == today, logs)
        logs = sorted(logs, key=lambda l: l.update_time)
        return u'\n'.join(map(lambda l: u'{} {} {}'.format(l.update_time, l.user.name, l.status), logs))


@admin_command(u'今日加班', u'', u'查询今天加班情况')
def on_query_overtime():
    with db_factory.create_session() as session:
        today = datetime.now().date()
        overtimes = session.query(UserOvertime).filter(UserOvertime.date == today).all()
        overtimes = sorted(overtimes, key=lambda o: o.book_time)
        return u'\n'.join(map(lambda l: u'{} {} {}'.format(l.book_time, l.user.name, l.type), overtimes))


@admin_command(u'本周外出', u'', u'查询本周末外出报人情况')
def query_current_weekend_out():
    with db_factory.create_session() as session:
        current_weekend_date = WeekendOut.current_weekend_date()
        weekend_outs = session.query(WeekendOut).filter(WeekendOut.weekend_date == current_weekend_date).all()
        weekend_outs = sorted(weekend_outs, key=lambda w: (w.location, w.user.name))
        return u'\n'.join(map(lambda w: u'{} {} {}'.format(w.user.name, w.type, w.location), weekend_outs))


@admin_command(u'报平安', u'')
def query_location_records():
    with db_factory.create_session() as session:
        users = session.query(User).all()
        users = filter(lambda u: u.status in UserStatus.report_names, users)
        all_names = set(map(lambda u: u.name, users))

        today = datetime.now().date()
        location_records = session.query(LocationRecord).filter(LocationRecord.book_date == today).all()
        location_records = sorted(location_records, key=lambda l: l.book_time)
        recorded_names = set(map(lambda l: l.user.name, location_records))

        no_recorded_names = all_names - recorded_names
        result = u'{}：共{}人，已有{}人报平安'.format(DEPARTMENT_NAME, len(all_names), len(recorded_names))
        if len(no_recorded_names):
            result += u'，{}人未报平安（{}）'.format(len(no_recorded_names), u'，'.join(no_recorded_names))

        result += u'。'

        if len(recorded_names):
            for record in location_records:
                result += u'\n{} {} {}'.format(record.book_time, record.user.name, record.location)

        return result


@admin_command(u'指南', u'')
def view_user_tutorial():
    return u'''革命小将：

    
带头大哥：
'''


@admin_command(u'登记加班', u'<任务> <姓名，逗号（,或，）分隔> [次数，默认1次] [日期，默认当天]')
def record_task(task_name, names, times=1, date=None):
    names = re.split(u'[,，]', names)
    if date:
        date = datetime.strptime(date, '%Y%m%d')
    else:
        date = datetime.now().date()

    with db_factory.create_session() as session:
        users = []
        for name in names:
            user = session.query(User).filter(User.name == name).scalar()
            if user:
                users.append(user)
            else:
                raise Exception(u'不存在名为{}的人'.format(name))

        for user in users:
            record = TaskRecord.new(user, task_name, times, date)
            session.add(record)


@admin_command(u'', u'', u'')
def list_task_record():
    pass


@admin_command(u'', u'', u'')
def clear_task_record():
    pass


@auto_self_command(u'加班地点', u'[地点]', u'')
def set_overtime_place(session, user, place=None):
    if place is not None:
        user.overtime_place = place
    else:
        return user.overtime_place